{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deep Learning with PyTorch Step-by-Step: A Beginner's Guide"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chapter 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    import google.colab\n",
    "    import requests\n",
    "    url = 'https://raw.githubusercontent.com/dvgodoy/PyTorchStepByStep/master/config.py'\n",
    "    r = requests.get(url, allow_redirects=True)\n",
    "    open('config.py', 'wb').write(r.content)    \n",
    "except ModuleNotFoundError:\n",
    "    pass\n",
    "\n",
    "from config import *\n",
    "config_chapter2()\n",
    "# This is needed to render the plots in this chapter\n",
    "from plots.chapter2 import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sklearn.linear_model import LinearRegression\n",
    "\n",
    "import torch\n",
    "import torch.optim as optim\n",
    "import torch.nn as nn\n",
    "from torch.utils.data import Dataset, TensorDataset, DataLoader\n",
    "from torch.utils.data.dataset import random_split\n",
    "from torch.utils.tensorboard import SummaryWriter\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "plt.style.use('fivethirtyeight')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Rethinking the Training Loop"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Training V0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Runs data generation - so we do not need to copy code here\n",
    "%run -i data_generation/simple_linear_regression.py\n",
    "\n",
    "# Runs the first two parts of the sequence: data preparation and model configuration\n",
    "%run -i data_preparation/v0.py\n",
    "%run -i model_configuration/v0.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load model_training/v0.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 1000\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "    # Sets model to TRAIN mode\n",
    "    model.train()\n",
    "\n",
    "    # Step 1 - Computes our model's predicted output - forward pass\n",
    "    # No more manual prediction!\n",
    "    yhat = model(x_train_tensor)\n",
    "    \n",
    "    # Step 2 - Computes the loss\n",
    "    loss = loss_fn(yhat, y_train_tensor)\n",
    "\n",
    "    # Step 3 - Computes gradients for both \"a\" and \"b\" parameters\n",
    "    loss.backward()\n",
    "    \n",
    "    # Step 4 - Updates parameters using gradients and the learning rate\n",
    "    optimizer.step()\n",
    "    optimizer.zero_grad()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9690]])), ('0.bias', tensor([1.0235]))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Higher-Order Functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def square(x):\n",
    "    return x ** 2\n",
    "\n",
    "def cube(x):\n",
    "    return x ** 3\n",
    "\n",
    "def fourth_power(x):\n",
    "    return x ** 4\n",
    "\n",
    "# and so on and so forth..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def generic_exponentiation(x, exponent):\n",
    "    return x ** exponent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def skeleton_exponentiation(x):\n",
    "    return x ** exponent"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "ename": "NameError",
     "evalue": "name 'exponent' is not defined",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m~/projects/PyTorchStepByStep/model_configuration/v0.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mskeleton_exponentiation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m~/projects/PyTorchStepByStep/model_configuration/v0.py\u001b[0m in \u001b[0;36mskeleton_exponentiation\u001b[0;34m(x)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mskeleton_exponentiation\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m     \u001b[0;32mreturn\u001b[0m \u001b[0mx\u001b[0m \u001b[0;34m**\u001b[0m \u001b[0mexponent\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;31mNameError\u001b[0m: name 'exponent' is not defined"
     ]
    }
   ],
   "source": [
    "skeleton_exponentiation(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def exponentiation_builder(exponent):\n",
    "    def skeleton_exponentiation(x):\n",
    "        return x ** exponent\n",
    "\n",
    "    return skeleton_exponentiation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.exponentiation_builder.<locals>.skeleton_exponentiation(x)>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "returned_function = exponentiation_builder(2)\n",
    "\n",
    "returned_function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "25"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "returned_function(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "square = exponentiation_builder(2)\n",
    "cube = exponentiation_builder(3)\n",
    "fourth_power = exponentiation_builder(4)\n",
    "\n",
    "# and so on and so forth..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Helper Function #1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_train_step_fn(model, loss_fn, optimizer):\n",
    "    # Builds function that performs a step in the train loop\n",
    "    def perform_train_step_fn(x, y):\n",
    "        # Sets model to TRAIN mode\n",
    "        model.train()\n",
    "        \n",
    "        # Step 1 - Computes our model's predicted output - forward pass\n",
    "        yhat = model(x)\n",
    "        # Step 2 - Computes the loss\n",
    "        loss = loss_fn(yhat, y)\n",
    "        # Step 3 - Computes gradients for both \"a\" and \"b\" parameters\n",
    "        loss.backward()\n",
    "        # Step 4 - Updates parameters using gradients and the learning rate\n",
    "        optimizer.step()\n",
    "        optimizer.zero_grad()\n",
    "        \n",
    "        # Returns the loss\n",
    "        return loss.item()\n",
    "    \n",
    "    # Returns the function that will be called inside the train loop\n",
    "    return perform_train_step_fn"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Configuration V1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i data_preparation/v0.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting model_configuration/v1.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_configuration/v1.py\n",
    "\n",
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "# Sets learning rate - this is \"eta\" ~ the \"n\" like Greek letter\n",
    "lr = 0.1\n",
    "\n",
    "torch.manual_seed(42)\n",
    "# Now we can create a model and send it at once to the device\n",
    "model = nn.Sequential(nn.Linear(1, 1)).to(device)\n",
    "\n",
    "# Defines a SGD optimizer to update the parameters (now retrieved directly from the model)\n",
    "optimizer = optim.SGD(model.parameters(), lr=lr)\n",
    "\n",
    "# Defines a MSE loss function\n",
    "loss_fn = nn.MSELoss(reduction='mean')\n",
    "\n",
    "# Creates the train_step function for our model, loss function and optimizer\n",
    "train_step_fn = make_train_step_fn(model, loss_fn, optimizer)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_configuration/v1.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.make_train_step_fn.<locals>.perform_train_step_fn(x, y)>"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_step_fn"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Training V1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting model_training/v1.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_training/v1.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 1000\n",
    "\n",
    "losses = []\n",
    "\n",
    "# For each epoch...\n",
    "for epoch in range(n_epochs):\n",
    "    # Performs one train step and returns the corresponding loss\n",
    "    loss = train_step_fn(x_train_tensor, y_train_tensor)\n",
    "    losses.append(loss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_training/v1.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9690]])), ('0.bias', tensor([1.0235]))])\n"
     ]
    }
   ],
   "source": [
    "# Checks model's parameters\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.7713]), tensor([2.4745]))\n"
     ]
    }
   ],
   "source": [
    "class CustomDataset(Dataset):\n",
    "    def __init__(self, x_tensor, y_tensor):\n",
    "        self.x = x_tensor\n",
    "        self.y = y_tensor\n",
    "        \n",
    "    def __getitem__(self, index):\n",
    "        return (self.x[index], self.y[index])\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "\n",
    "# Wait, is this a CPU tensor now? Why? Where is .to(device)?\n",
    "x_train_tensor = torch.from_numpy(x_train).float()\n",
    "y_train_tensor = torch.from_numpy(y_train).float()\n",
    "\n",
    "train_data = CustomDataset(x_train_tensor, y_train_tensor)\n",
    "print(train_data[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## TensorDataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(tensor([0.7713]), tensor([2.4745]))\n"
     ]
    }
   ],
   "source": [
    "train_data = TensorDataset(x_train_tensor, y_train_tensor)\n",
    "print(train_data[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DataLoader"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_loader = DataLoader(dataset=train_data, batch_size=16, shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[tensor([[0.9507],\n",
       "         [0.5427],\n",
       "         [0.1409],\n",
       "         [0.3745],\n",
       "         [0.1987],\n",
       "         [0.8948],\n",
       "         [0.7722],\n",
       "         [0.7852],\n",
       "         [0.5248],\n",
       "         [0.2809],\n",
       "         [0.1159],\n",
       "         [0.0740],\n",
       "         [0.1849],\n",
       "         [0.4561],\n",
       "         [0.7608],\n",
       "         [0.1560]]), tensor([[2.8715],\n",
       "         [2.2161],\n",
       "         [1.1211],\n",
       "         [1.7578],\n",
       "         [1.2654],\n",
       "         [2.7393],\n",
       "         [2.4208],\n",
       "         [2.5283],\n",
       "         [2.0167],\n",
       "         [1.5846],\n",
       "         [1.1603],\n",
       "         [1.1713],\n",
       "         [1.5888],\n",
       "         [1.7706],\n",
       "         [2.4970],\n",
       "         [1.2901]])]"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "next(iter(train_loader))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Data Preparation V1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting data_preparation/v1.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile data_preparation/v1.py\n",
    "\n",
    "# Our data was in Numpy arrays, but we need to transform them into PyTorch's Tensors\n",
    "x_train_tensor = torch.from_numpy(x_train).float()\n",
    "y_train_tensor = torch.from_numpy(y_train).float()\n",
    "\n",
    "# Builds Dataset\n",
    "train_data = TensorDataset(x_train_tensor, y_train_tensor)\n",
    "\n",
    "# Builds DataLoader\n",
    "train_loader = DataLoader(dataset=train_data, batch_size=16, shuffle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i data_preparation/v1.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Training V2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_configuration/v1.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Overwriting model_training/v2.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_training/v2.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 1000\n",
    "\n",
    "losses = []\n",
    "\n",
    "# For each epoch...\n",
    "for epoch in range(n_epochs):\n",
    "    # inner loop\n",
    "    mini_batch_losses = []\n",
    "    for x_batch, y_batch in train_loader:\n",
    "        # the dataset \"lives\" in the CPU, so do our mini-batches\n",
    "        # therefore, we need to send those mini-batches to the\n",
    "        # device where the model \"lives\"\n",
    "        x_batch = x_batch.to(device)\n",
    "        y_batch = y_batch.to(device)\n",
    "\n",
    "        # Performs one train step and returns the corresponding loss \n",
    "        # for this mini-batch\n",
    "        mini_batch_loss = train_step_fn(x_batch, y_batch)\n",
    "        mini_batch_losses.append(mini_batch_loss)\n",
    "\n",
    "    # Computes average loss over all mini-batches - that's the epoch loss\n",
    "    loss = np.mean(mini_batch_losses)\n",
    "    \n",
    "    losses.append(loss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_training/v2.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9694]])), ('0.bias', tensor([1.0234]))])\n"
     ]
    }
   ],
   "source": [
    "# Checks model's parameters\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Mini-Batch Inner Loop"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Helper Function #2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mini_batch(device, data_loader, step_fn):\n",
    "    mini_batch_losses = []\n",
    "    for x_batch, y_batch in data_loader:\n",
    "        x_batch = x_batch.to(device)\n",
    "        y_batch = y_batch.to(device)\n",
    "\n",
    "        mini_batch_loss = step_fn(x_batch, y_batch)\n",
    "        mini_batch_losses.append(mini_batch_loss)\n",
    "\n",
    "    loss = np.mean(mini_batch_losses)\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Training V3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i data_preparation/v1.py\n",
    "%run -i model_configuration/v1.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing model_training/v3.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_training/v3.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 200\n",
    "\n",
    "losses = []\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "    # inner loop\n",
    "    loss = mini_batch(device, train_loader, train_step_fn)\n",
    "    losses.append(loss)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_training/v3.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9687]], device='cuda:0')), ('0.bias', tensor([1.0236], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "# Checks model's parameters\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Random Split"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Data Preparation V2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing data_preparation/v2.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile data_preparation/v2.py\n",
    "\n",
    "torch.manual_seed(13)\n",
    "\n",
    "# Builds tensors from numpy arrays BEFORE split\n",
    "x_tensor = torch.from_numpy(x).float()\n",
    "y_tensor = torch.from_numpy(y).float()\n",
    "\n",
    "# Builds dataset containing ALL data points\n",
    "dataset = TensorDataset(x_tensor, y_tensor)\n",
    "\n",
    "# Performs the split\n",
    "ratio = .8\n",
    "n_total = len(dataset)\n",
    "n_train = int(n_total * ratio)\n",
    "n_val = n_total - n_train\n",
    "\n",
    "train_data, val_data = random_split(dataset, [n_train, n_val])\n",
    "\n",
    "# Builds a loader of each set\n",
    "train_loader = DataLoader(dataset=train_data, batch_size=16, shuffle=True)\n",
    "val_loader = DataLoader(dataset=val_data, batch_size=16)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i data_preparation/v2.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Evaluation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Helper Function #3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_val_step_fn(model, loss_fn):\n",
    "    # Builds function that performs a step in the validation loop\n",
    "    def perform_val_step_fn(x, y):\n",
    "        # Sets model to EVAL mode\n",
    "        model.eval()\n",
    "        \n",
    "        # Step 1 - Computes our model's predicted output - forward pass\n",
    "        yhat = model(x)\n",
    "        # Step 2 - Computes the loss\n",
    "        loss = loss_fn(yhat, y)\n",
    "        # There is no need to compute Steps 3 and 4, since we don't update parameters during evaluation\n",
    "        return loss.item()\n",
    "    \n",
    "    return perform_val_step_fn"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Configuration V2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing model_configuration/v2.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_configuration/v2.py\n",
    "\n",
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "# Sets learning rate - this is \"eta\" ~ the \"n\" like Greek letter\n",
    "lr = 0.1\n",
    "\n",
    "torch.manual_seed(42)\n",
    "# Now we can create a model and send it at once to the device\n",
    "model = nn.Sequential(nn.Linear(1, 1)).to(device)\n",
    "\n",
    "# Defines a SGD optimizer to update the parameters (now retrieved directly from the model)\n",
    "optimizer = optim.SGD(model.parameters(), lr=lr)\n",
    "\n",
    "# Defines a MSE loss function\n",
    "loss_fn = nn.MSELoss(reduction='mean')\n",
    "\n",
    "# Creates the train_step function for our model, loss function and optimizer\n",
    "train_step_fn = make_train_step_fn(model, loss_fn, optimizer)\n",
    "\n",
    "# Creates the val_step function for our model and loss function\n",
    "val_step_fn = make_val_step_fn(model, loss_fn)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_configuration/v2.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Training V4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing model_training/v4.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_training/v4.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 200\n",
    "\n",
    "losses = []\n",
    "val_losses = []\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "    # inner loop\n",
    "    loss = mini_batch(device, train_loader, train_step_fn)\n",
    "    losses.append(loss)\n",
    "    \n",
    "    # VALIDATION\n",
    "    # no gradients in validation!\n",
    "    with torch.no_grad():\n",
    "        val_loss = mini_batch(device, val_loader, val_step_fn)\n",
    "        val_losses.append(val_loss)    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_training/v4.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9419]], device='cuda:0')), ('0.bias', tensor([1.0244], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "# Checks model's parameters\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plotting Losses"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plot_losses(losses, val_losses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# TensorBoard"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tensorboard_cleanup()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "      <iframe id=\"tensorboard-frame-b9df1d0b3c7a9074\" width=\"100%\" height=\"800\" frameborder=\"0\">\n",
       "      </iframe>\n",
       "      <script>\n",
       "        (function() {\n",
       "          const frame = document.getElementById(\"tensorboard-frame-b9df1d0b3c7a9074\");\n",
       "          const url = new URL(\"/\", window.location);\n",
       "          url.port = 6006;\n",
       "          frame.src = url;\n",
       "        })();\n",
       "      </script>\n",
       "  "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "if IS_BINDER:\n",
    "    display(TB_LINK)\n",
    "else:\n",
    "    %load_ext tensorboard\n",
    "    %tensorboard --logdir runs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## SummaryWriter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [],
   "source": [
    "writer = SummaryWriter('runs/test')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## add_graph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'NoneType' object is not iterable",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "\u001b[0;32m~/projects/PyTorchStepByStep/model_training/v4.py\u001b[0m in \u001b[0;36m<module>\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mwriter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[0;32m~/anaconda3/envs/torch/lib/python3.7/site-packages/torch/utils/tensorboard/writer.py\u001b[0m in \u001b[0;36madd_graph\u001b[0;34m(self, model, input_to_model, verbose)\u001b[0m\n\u001b[1;32m    680\u001b[0m         \u001b[0;32mif\u001b[0m \u001b[0mhasattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'forward'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    681\u001b[0m             \u001b[0;31m# A valid PyTorch model should have a 'forward' method\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 682\u001b[0;31m             \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_file_writer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0madd_graph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mgraph\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0minput_to_model\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mverbose\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    683\u001b[0m         \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    684\u001b[0m             \u001b[0;31m# Caffe2 models do not have the 'forward' method\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/envs/torch/lib/python3.7/site-packages/torch/utils/tensorboard/_pytorch_graph.py\u001b[0m in \u001b[0;36mgraph\u001b[0;34m(model, args, verbose)\u001b[0m\n\u001b[1;32m    232\u001b[0m     \u001b[0;32mwith\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0monnx\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_training\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m  \u001b[0;31m# TODO: move outside of torch.onnx?\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    233\u001b[0m         \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 234\u001b[0;31m             \u001b[0mtrace\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtorch\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mjit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtrace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0margs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    235\u001b[0m             \u001b[0mgraph\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtrace\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mgraph\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    236\u001b[0m         \u001b[0;32mexcept\u001b[0m \u001b[0mRuntimeError\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/envs/torch/lib/python3.7/site-packages/torch/jit/__init__.py\u001b[0m in \u001b[0;36mtrace\u001b[0;34m(func, example_inputs, optimize, check_trace, check_inputs, check_tolerance, _force_outplace, _module_class, _compilation_unit)\u001b[0m\n\u001b[1;32m    856\u001b[0m         return trace_module(func, {'forward': example_inputs}, None,\n\u001b[1;32m    857\u001b[0m                             \u001b[0mcheck_trace\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mwrap_check_inputs\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcheck_inputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 858\u001b[0;31m                             check_tolerance, _force_outplace, _module_class)\n\u001b[0m\u001b[1;32m    859\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    860\u001b[0m     if (hasattr(func, '__self__') and isinstance(func.__self__, torch.nn.Module) and\n",
      "\u001b[0;32m~/anaconda3/envs/torch/lib/python3.7/site-packages/torch/jit/__init__.py\u001b[0m in \u001b[0;36mtrace_module\u001b[0;34m(mod, inputs, optimize, check_trace, check_inputs, check_tolerance, _force_outplace, _module_class, _compilation_unit)\u001b[0m\n\u001b[1;32m    994\u001b[0m         \u001b[0;31m# this is needed since Module.__call__ sets up some extra tracing\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    995\u001b[0m         \u001b[0mfunc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmod\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mmethod_name\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m\"forward\"\u001b[0m \u001b[0;32melse\u001b[0m \u001b[0mgetattr\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmod\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmethod_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 996\u001b[0;31m         \u001b[0mexample_inputs\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmake_tuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexample_inputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    997\u001b[0m         \u001b[0mmodule\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_c\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_create_method_from_trace\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmethod_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mfunc\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mexample_inputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mvar_lookup_fn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0m_force_outplace\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    998\u001b[0m         \u001b[0mcheck_trace_method\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmodule\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_c\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_get_method\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmethod_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;32m~/anaconda3/envs/torch/lib/python3.7/site-packages/torch/jit/__init__.py\u001b[0m in \u001b[0;36mmake_tuple\u001b[0;34m(example_inputs)\u001b[0m\n\u001b[1;32m    695\u001b[0m     \u001b[0;31m# done primarily so that weird iterables fail here and not pybind11 code\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    696\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0misinstance\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexample_inputs\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 697\u001b[0;31m         \u001b[0;32mreturn\u001b[0m \u001b[0mtuple\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mexample_inputs\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m    698\u001b[0m     \u001b[0;32mreturn\u001b[0m \u001b[0mexample_inputs\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m    699\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31mTypeError\u001b[0m: 'NoneType' object is not iterable"
     ]
    }
   ],
   "source": [
    "writer.add_graph(model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Fetching a tuple of feature (sample_x) and label (sample_y)\n",
    "sample_x, sample_y = next(iter(train_loader))\n",
    "\n",
    "# Since our model was sent to device, we need to do the same with the data\n",
    "# Even here, both model and data need to be on the same device!\n",
    "writer.add_graph(model, sample_x.to(device))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## add_scalars"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "writer.add_scalars('loss', {'training': loss, 'validation': val_loss}, epoch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Configuration V3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%run -i data_preparation/v2.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing model_configuration/v3.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_configuration/v3.py\n",
    "\n",
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "# Sets learning rate - this is \"eta\" ~ the \"n\" like Greek letter\n",
    "lr = 0.1\n",
    "\n",
    "torch.manual_seed(42)\n",
    "# Now we can create a model and send it at once to the device\n",
    "model = nn.Sequential(nn.Linear(1, 1)).to(device)\n",
    "\n",
    "# Defines a SGD optimizer to update the parameters (now retrieved directly from the model)\n",
    "optimizer = optim.SGD(model.parameters(), lr=lr)\n",
    "\n",
    "# Defines a MSE loss function\n",
    "loss_fn = nn.MSELoss(reduction='mean')\n",
    "\n",
    "# Creates the train_step function for our model, loss function and optimizer\n",
    "train_step_fn = make_train_step_fn(model, loss_fn, optimizer)\n",
    "\n",
    "# Creates the val_step function for our model and loss function\n",
    "val_step_fn = make_val_step_fn(model, loss_fn)\n",
    "\n",
    "# Creates a Summary Writer to interface with TensorBoard\n",
    "writer = SummaryWriter('runs/simple_linear_regression')\n",
    "\n",
    "# Fetches a single mini-batch so we can use add_graph\n",
    "x_sample, y_sample = next(iter(train_loader))\n",
    "writer.add_graph(model, x_sample.to(device))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_configuration/v3.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Model Training V5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Writing model_training/v5.py\n"
     ]
    }
   ],
   "source": [
    "%%writefile model_training/v5.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 200\n",
    "\n",
    "losses = []\n",
    "val_losses = []\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "    # inner loop\n",
    "    loss = mini_batch(device, train_loader, train_step_fn)\n",
    "    losses.append(loss)\n",
    "    \n",
    "    # VALIDATION\n",
    "    # no gradients in validation!\n",
    "    with torch.no_grad():\n",
    "        val_loss = mini_batch(device, val_loader, val_step_fn)\n",
    "        val_losses.append(val_loss)\n",
    "    \n",
    "    # Records both losses for each epoch under the main tag \"loss\"\n",
    "    writer.add_scalars(main_tag='loss',\n",
    "                       tag_scalar_dict={'training': loss, 'validation': val_loss},\n",
    "                       global_step=epoch)\n",
    "\n",
    "# Closes the writer\n",
    "writer.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_training/v5.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9448]], device='cuda:0')), ('0.bias', tensor([1.0295], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "# Checks model's parameters\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Saving and Loading Models"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Saving"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "checkpoint = {'epoch': n_epochs,\n",
    "              'model_state_dict': model.state_dict(),\n",
    "              'optimizer_state_dict': optimizer.state_dict(),\n",
    "              'loss': losses,\n",
    "              'val_loss': val_losses}\n",
    "\n",
    "torch.save(checkpoint, 'model_checkpoint.pth')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Resuming Training"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i data_preparation/v2.py\n",
    "%run -i model_configuration/v3.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[0.7645]], device='cuda:0')), ('0.bias', tensor([0.8300], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Sequential(\n",
       "  (0): Linear(in_features=1, out_features=1, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "checkpoint = torch.load('model_checkpoint.pth', weights_only=False)\n",
    "\n",
    "model.load_state_dict(checkpoint['model_state_dict'])\n",
    "optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n",
    "\n",
    "saved_epoch = checkpoint['epoch']\n",
    "saved_losses = checkpoint['loss']\n",
    "saved_val_losses = checkpoint['val_loss']\n",
    "\n",
    "model.train() # always use TRAIN for resuming training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9448]], device='cuda:0')), ('0.bias', tensor([1.0295], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.7"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run -i model_training/v5.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9448]], device='cuda:0')), ('0.bias', tensor([1.0295], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plot_resumed_losses(saved_epoch, saved_losses, saved_val_losses, n_epochs, losses, val_losses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Deploying / Making Predictions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.8"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "%run -i model_configuration/v3.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.9"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9448]], device='cuda:0')), ('0.bias', tensor([1.0295], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "checkpoint = torch.load('model_checkpoint.pth', weights_only=False)\n",
    "\n",
    "model.load_state_dict(checkpoint['model_state_dict'])\n",
    "\n",
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Cell 2.10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[1.4185],\n",
       "        [1.6908],\n",
       "        [2.1381]], device='cuda:0', grad_fn=<AddmmBackward>)"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "new_inputs = torch.tensor([[.20], [.34], [.57]])\n",
    "\n",
    "model.eval() # always use EVAL for fully trained models!\n",
    "model(new_inputs.to(device))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Putting It All Together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load data_preparation/v2.py\n",
    "\n",
    "torch.manual_seed(13)\n",
    "\n",
    "# Builds tensors from numpy arrays BEFORE split\n",
    "x_tensor = torch.from_numpy(x).float()\n",
    "y_tensor = torch.from_numpy(y).float()\n",
    "\n",
    "# Builds dataset containing ALL data points\n",
    "dataset = TensorDataset(x_tensor, y_tensor)\n",
    "\n",
    "# Performs the split\n",
    "ratio = .8\n",
    "n_total = len(dataset)\n",
    "n_train = int(n_total * ratio)\n",
    "n_val = n_total - n_train\n",
    "\n",
    "train_data, val_data = random_split(dataset, [n_train, n_val])\n",
    "\n",
    "# Builds a loader of each set\n",
    "train_loader = DataLoader(dataset=train_data, batch_size=16, shuffle=True)\n",
    "val_loader = DataLoader(dataset=val_data, batch_size=16)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load model_configuration/v3.py\n",
    "\n",
    "device = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "# Sets learning rate - this is \"eta\" ~ the \"n\" like Greek letter\n",
    "lr = 0.1\n",
    "\n",
    "torch.manual_seed(42)\n",
    "# Now we can create a model and send it at once to the device\n",
    "model = nn.Sequential(nn.Linear(1, 1)).to(device)\n",
    "\n",
    "# Defines a SGD optimizer to update the parameters (now retrieved directly from the model)\n",
    "optimizer = optim.SGD(model.parameters(), lr=lr)\n",
    "\n",
    "# Defines a MSE loss function\n",
    "loss_fn = nn.MSELoss(reduction='mean')\n",
    "\n",
    "# Creates the train_step function for our model, loss function and optimizer\n",
    "train_step_fn = make_train_step_fn(model, loss_fn, optimizer)\n",
    "\n",
    "# Creates the val_step function for our model and loss function\n",
    "val_step_fn = make_val_step_fn(model, loss_fn)\n",
    "\n",
    "# Creates a Summary Writer to interface with TensorBoard\n",
    "writer = SummaryWriter('runs/simple_linear_regression')\n",
    "\n",
    "# Fetches a single mini-batch so we can use add_graph\n",
    "x_sample, y_sample = next(iter(train_loader))\n",
    "writer.add_graph(model, x_sample.to(device))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %load model_training/v5.py\n",
    "\n",
    "# Defines number of epochs\n",
    "n_epochs = 200\n",
    "\n",
    "losses = []\n",
    "val_losses = []\n",
    "\n",
    "for epoch in range(n_epochs):\n",
    "    # inner loop\n",
    "    loss = mini_batch(device, train_loader, train_step_fn)\n",
    "    losses.append(loss)\n",
    "    \n",
    "    # VALIDATION\n",
    "    # no gradients in validation!\n",
    "    with torch.no_grad():\n",
    "        val_loss = mini_batch(device, val_loader, val_step_fn)\n",
    "        val_losses.append(val_loss)\n",
    "    \n",
    "    # Records both losses for each epoch under the main tag \"loss\"\n",
    "    writer.add_scalars(main_tag='loss',\n",
    "                       tag_scalar_dict={'training': loss, 'validation': val_loss},\n",
    "                       global_step=epoch)\n",
    "\n",
    "# Closes the writer\n",
    "writer.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OrderedDict([('0.weight', tensor([[1.9448]], device='cuda:0')), ('0.bias', tensor([1.0295], device='cuda:0'))])\n"
     ]
    }
   ],
   "source": [
    "print(model.state_dict())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Weird plots in TensorBoard?\n",
    "\n",
    "Run this if you want to clean up a previous run and start fresh with TensorBoard :-)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [],
   "source": [
    "import shutil\n",
    "\n",
    "shutil.rmtree('./runs/simple_linear_regression/', ignore_errors=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
